/* 
 * Author: vdaras
 */

#include "Component.h"

#include "../sdl/sdl_utilities.h"
#include "Container.h"
#include "ActionListener.h"
#include <algorithm>

namespace gui
{


/**
 * Constructor initializes the component's members using the parameter's
 * passed.
 *
 * @param name: component's name, an identifier for it in the system
 * @param w: component's width
 * @param h: component's height
 */

Component::Component(const std::string& name, int w, int h)
:
m_name(name),
m_parent(NULL),
m_x(0),
m_y(0),
m_width(w),
m_height(h),
m_visible(true),
m_hasFocus(false)
{
    m_visiblePart.x = m_visiblePart.y = 0;
    m_visiblePart.w = w;
    m_visiblePart.h = h;
}


/**
 * Constructor initializes the component's members using the parameter's
 * passed.
 *
 * @param parent: Component's 'parent node' in the composite
 * @param name: component's name, an identifier for it in the system
 * @param x: component's x coordinate inside its' container
 * @param y: component's y coordinate inside its' container
 * @param w: component's width
 * @param h: component's height
 */

Component::Component(const std::string &name, Container *parent, int x, int y, int w, int h)
:
m_name(name),
m_parent(NULL),
m_x(0),
m_y(0),
m_width(w),
m_height(h),
m_visible(true),
m_hasFocus(false)
{
    m_visiblePart.x = m_visiblePart.y = 0;
    m_visiblePart.w = w;
    m_visiblePart.h = h;
    
    parent->AddComponent( this, x, y );
}


Component::~Component()
{
}


/**
 * Returns the name of the component.
 *
 * @return name of the component.
 */

const std::string &Component::GetName() const
{
    return m_name;
}


/**
 * Returns the parent of this component.
 *
 * @return parent of the component.
 */

Container *Component::GetParent() const
{
    return m_parent;
}


/**
 * Returns the absolute screen position of a component.
 *
 * @param x: OUTPUT parameter, passed to load x on that var
 * @param y: OUTPUT parameter, passed to load y on that var
 */

void Component::GetPosition(int& x, int& y) const
{
    x = GetX( );
    y = GetY( );
}


/**
 * Returns relative coordinates from parent container.
 *
 * @param x: OUTPUT parameter, relative x is stored in here.
 * @param y: OUTPUT parameter, relative y is stored in here.
 */

void Component::GetRelativePosition(int &x, int &y) const
{
    x = m_x;
    y = m_y;
}


/**
 * Returns the absolute x screen coordinate of the component.
 *
 * @return absolute x of this comoponent.
 */

int Component::GetX() const
{
    return m_parent ? m_x + m_parent->GetX( ) : m_x;
}


/**
 * Returns the relative x screen coordinate of the component.
 *
 * @return relative x of this component.
 */

int Component::GetRelativeX() const
{
    return m_x;
}


/**
 * Retruns the absolute y screen coordinate of the component.
 *
 * @return absolute y of this component.
 */

int Component::GetY() const
{
    return m_parent ? m_y + m_parent->GetY( ) : m_y;
}


/**
 * Returns the relative y screen coordinate of the component.
 *
 * @return relative y of this component.
 */

int Component::GetRelativeY() const
{
    return m_y;
}


/**
 * Rerturns the width of the component.
 *
 * @return component's width.
 */

int Component::GetWidth() const
{
    return m_width;
}


/**
 * Returns the height of the component.
 *
 * @return component's height.
 */

int Component::GetHeight() const
{
    return m_height;
}


/**
 * Returns whether the Component is visible or not.
 *
 * @return a boolean value indicating visibility.
 */

bool Component::IsVisible() const
{
    return m_visible;
}


/**
 * Returns a rectangle containing the absolute boundaries of a Component.
 *
 * @return bounds of this component = { x, y, x + width, y + height };
 */

SDL_Rect Component::GetBounds() const
{
    int absoluteX = m_x + m_parent->GetX( );
    
    int absoluteY = m_y + m_parent->GetY( );
    
    SDL_Rect rect = { absoluteX, absoluteY, absoluteX + m_width, absoluteY + m_height };

    return rect;
}


/**
 * Returns a rectangle containing the relative boundaries of a Component.
 *
 * @return relative bounds of this component = { rel_x, rel_y, rel_x + width, rel_y + height }
 */

SDL_Rect Component::GetRelativeBounds() const
{
    SDL_Rect rect = { m_x, m_y, m_x + m_width, m_y + m_height };
    
    return rect;
}


/**
 * Returns the defininitive rectangle of this component.
 *
 * @return rectangle = { x, y, width, height }
 */

SDL_Rect Component::GetRect() const
{
    int absoluteX = m_x + m_parent->GetX( );
    
    int absoluteY = m_y + m_parent->GetY( );
    
    SDL_Rect rect = { absoluteX , absoluteY, m_width, m_height };
    
    return rect;
}


/**
 * Returns the defininitive rectangle of this component, in relation to its parent.
 *
 * @return rectangle = { rel_x, rel_y, width, height }
 */

SDL_Rect Component::GetRelativeRect() const
{
    SDL_Rect rect = { m_x, m_y, m_width, m_height };
    
    return rect;
}



/**
 * Sets the name of the component.
 *
 * @param name: to set
 */

void Component::SetName(const std::string& name)
{
    m_name = name;
}


/**
 * Sets the parent of the component.
 *
 * @param parent: to set
 */

void Component::SetParent(Container* parent)
{
    m_parent = parent;
}


/**
 * Sets relative to parent x position of the Component.
 *
 * @param x: to set
 */

void Component::SetX(int x)
{
    m_x = x;
    
    UpdateVisiblePart( );
}


/**
 * Sets relative to parent y position of the Component.
 *
 * @param y: to set
 */

void Component::SetY(int y)
{
    m_y = y;
    
    UpdateVisiblePart( );
}


/**
 * Sets the position of the component inside it's container. The x and y coord
 * values are converted to absolute screen coordinates.
 *
 * @param x: new x coordinate
 * @param y: new y coordinate
 */

void Component::SetPosition(int x, int y)
{
    SetX( x );
    SetY( y );
}


/**
 * This routine makes the Component visible.
 */

void Component::Show()
{
    m_visible = true;
}


/**
 * This routine makes the Component invisible
 */

void Component::Hide()
{
    m_visible = false;
}


/**
 * This routine attaches an Action Listener to this Component
 */

void Component::AddActionListener(ActionListener* toAdd)
{
    //append Action Listener to the listener list
    m_listeners.push_back( toAdd );
}


/**
 * This routine detaches an Action Listener from this Component
 */

void Component::RemoveActionListener(ActionListener* toRemove)
{
    //remove Action Listener from the listener's list
    m_listeners.remove( toRemove );
}


/**
 * This is invoked for a Component when a mouse event occurrs and the
 * Component has focus. Override to define specific component behaviour.
 *
 * @param event: the mouse event occurred.
 */

void Component::OnMouseEvent(MouseEvent& event)
{
    
}


/**
 * This is invoked for a Component when a keyboard event occurrs and the
 * Component has focus. Override to define specific component behaviour.
 *
 * @param event: the keyboard event occurred.
 */ 

void Component::OnKeyboardEvent(KeyboardEvent& event)
{
    
}


/**
 * Gives focus to this component.
 */

void Component::Focus()
{
    m_hasFocus = true;
    
    Container *grandparent = m_parent->GetParent( );
    
    if( grandparent )
    {
	grandparent->BringToFront( m_parent );
    }
}


/** 
 * Unfocuses this component.
 */

void Component::Unfocus()
{
    m_hasFocus = false;
}



/**
 * @return a boolean value indicating if the component has focus or not.
 */

bool Component::HasFocus() const
{
    return m_hasFocus;
}


/**
 * Routine called when needed to recompute the visible part of this Component.
 */

void Component::UpdateVisiblePart()
{
    if( m_parent )
    {
	SDL_Rect parentVisiblePart = m_parent->GetRelVisibleRect( );
	
	SDL_Rect myRelativeBounds = GetRelativeBounds( );
	
	if( myRelativeBounds.x < parentVisiblePart.x )
	{
	    m_visiblePart.x = std::abs( myRelativeBounds.x ) + parentVisiblePart.x;
	}
	
	if( myRelativeBounds.y < parentVisiblePart.y )
	{
	    m_visiblePart.y = std::abs( myRelativeBounds.y ) + parentVisiblePart.y;
	}
	
	if( myRelativeBounds.w > parentVisiblePart.w )
	{
	    m_visiblePart.w = parentVisiblePart.w - myRelativeBounds.x;
	}
	
	if( myRelativeBounds.h > parentVisiblePart.h )
	{
	    m_visiblePart.h = parentVisiblePart.h - myRelativeBounds.y;
	}
    }
}


/**
 * This is used to offer run time information about a component. If this is a regular Component
 * NULL is returned. Container should override this one in order to return a pointer to itself.
 */

Container *Component::GetContainer()
{
    return NULL;
}


/**
 * @param event: a MouseEvent occurred.
 * @param mouseX: mouse X coordinate.
 * @param mouseY: mouse Y coordinate.
 */

bool Component::NeedsFocus(int mouseX, int mouseY) const
{
    SDL_Rect absoluteVisiblePart = GetVisibleRect( );
    
   return sdl::PointInRect( mouseX, mouseY, &absoluteVisiblePart );
}


/**
 * Sets the width of the component.
 *
 * @param width: to set
 */

void Component::SetWidth(int width)
{
    m_width = width;
    
    UpdateVisiblePart( );
}


/**
 * Sets the height of the component.
 *
 * @param height: to set
 */

void Component::SetHeight(int height)
{
    m_height = height;
    
    UpdateVisiblePart( );
}


/**
 * Returns the visible part of this Component.
 *
 * @return the rectangle containing the visible part.
 */

SDL_Rect Component::GetVisibleRect() const
{
    SDL_Rect ret;
    
    if( m_parent != NULL )
    {
        ret.x = m_parent->GetX( ) + m_x + m_visiblePart.x;
	ret.y = m_parent->GetY( ) + m_y + m_visiblePart.y;
	ret.w = m_visiblePart.w;
	ret.h = m_visiblePart.h;
    }
    else
    {
	ret = m_visiblePart;
    }
    
    return ret;
}


/**
 * Returns the visible part of this Component. The coordinates
 * of the rectangle returned are relative to the Componenent's parent
 *
 * @return the rectangle containing the relative coordinates of the visible part.
 */

SDL_Rect Component::GetRelVisibleRect() const
{
    return m_visiblePart;
}


};
